---
title: usePipecatEventStream
description: Hook to subscribe to Pipecat RTVI events with throttled updates and optional grouping.
---

## Overview

`usePipecatEventStream` subscribes to Pipecat RTVI events and exposes a throttled snapshot of recent events. It minimizes re-renders during high-frequency event bursts and can optionally group consecutive events by a key.

## Usage

### Basic

```tsx
import { usePipecatEventStream } from "@pipecat-ai/voice-ui-kit";

export function RecentEvents() {
  const { events, clear } = usePipecatEventStream({ maxEvents: 100 });
  return (
    <div>
      <button onClick={clear}>Clear</button>
      <ul>
        {events.map((e) => (
          <li key={e.id}>
            <strong>{e.type}</strong>
            <span> – {e.timestamp.toLocaleTimeString()}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### Filter and Throttle

```tsx
const { events } = usePipecatEventStream({
  includeEvents: ["serverMessage", "serverResponse"],
  throttleMs: 100, // batch UI updates every 100ms
});
```

### Group Consecutive

```tsx
const { groups } = usePipecatEventStream({
  groupConsecutive: true,
  groupKey: (e) => e.type,
});
```

### Full example

```tsx
import React, { useEffect, useRef, useState } from "react";
import {
  usePipecatEventStream,
  type PipecatEventGroup,
} from "@pipecat-ai/voice-ui-kit";

export function EventStreamExample() {
  const { events, groups } = usePipecatEventStream({
    maxEvents: 200,
    groupConsecutive: true,
  });
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const endRef = useRef<HTMLDivElement>(null);

  // Auto-scroll on new events
  useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [events]);

  const toggleGroup = (id: string) => {
    setExpanded((prev) => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  };

  return (
    <div style={{ height: 300, overflow: "auto", fontFamily: "monospace", fontSize: 12 }}>
      {groups.map((group: PipecatEventGroup) => {
        const isExpanded = expanded.has(group.id);
        const hasMultiple = group.events.length > 1;

        if (!hasMultiple || isExpanded) {
          return group.events.map((e, idx) => (
            <div key={e.id}>
              {hasMultiple && idx === 0 ? (
                <button onClick={() => toggleGroup(group.id)}>[−]</button>
              ) : (
                <span>•</span>
              )}
              <span> [{e.timestamp.toLocaleTimeString()}] </span>
              <strong>{e.type}</strong> {e.data ? JSON.stringify(e.data) : "null"}
            </div>
          ));
        }

        return (
          <div key={group.id}>
            <button onClick={() => toggleGroup(group.id)}>[+]</button>
            <span> [{group.events[0].timestamp.toLocaleTimeString()}] </span>
            <strong>{group.type}</strong> ({group.events.length} events)
          </div>
        );
      })}
      <div ref={endRef} />
    </div>
  );
}
```

---

## API

### Options

<TypeTable
  className="text-sm"
  type={{
    maxEvents: {
      description: "Maximum number of recent events to retain",
      type: "number",
      required: false,
      default: "500",
    },
    ignoreEvents: {
      description: "Events to ignore (ignored unless includeEvents is empty)",
      type: "string[]",
      required: false,
      default: "[RTVIEvent.LocalAudioLevel]",
    },
    includeEvents: {
      description: "Whitelist of events to include (takes precedence over ignoreEvents)",
      type: "string[]",
      required: false,
      default: "undefined",
    },
    paused: {
      description: "Pause capturing without tearing down subscriptions",
      type: "boolean",
      required: false,
      default: "false",
    },
    mapEventData: {
      description: "Optional transform to sanitize/shape event data before storing",
      type: "(data: unknown, eventType: string) => unknown",
      required: false,
      default: "undefined",
    },
    throttleMs: {
      description: "Throttle UI notifications; 0 uses requestAnimationFrame",
      type: "number",
      required: false,
      default: "0",
    },
    onEvent: {
      description: "Callback invoked for each captured event (after storing)",
      type: "(event: PipecatEventLog) => void",
      required: false,
      default: "undefined",
    },
    groupConsecutive: {
      description: "When true, also compute and return consecutive groups",
      type: "boolean",
      required: false,
      default: "false",
    },
    groupKey: {
      description: "Grouping key selector; defaults to event.type",
      type: "(event: PipecatEventLog) => string",
      required: false,
      default: "(e) => e.type",
    },
  }}
/>

### Return

<TypeTable
  className="text-sm"
  type={{
    events: {
      description: "Frozen snapshot of recent events",
      type: "ReadonlyArray<PipecatEventLog>",
      required: true,
    },
    groups: {
      description: "Consecutive groups (empty unless groupConsecutive is true)",
      type: "ReadonlyArray<PipecatEventGroup>",
      required: true,
    },
    clear: {
      description: "Clears the current event list",
      type: "() => void",
      required: true,
    },
  }}
/>


